﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.Common;
using ADOWrapper.ObjectResolver;

namespace ADOWrapper
{
    public class Database
    {
        DbProviderFactory dbProviderFactory;
        string connectionString;

        public Database(DbProviderFactory dbProviderFactory, string connectionString)
        {
            initialize(dbProviderFactory, connectionString);
        }

        public Database(DataRow providerRow, string connectionString)
        {
            initialize(DbProviderFactories.GetFactory(providerRow), connectionString);
        }

        public Database(string providerInvariantName, string connectionString)
        {
            initialize(DbProviderFactories.GetFactory(providerInvariantName), connectionString);
        }

        private void initialize(DbProviderFactory dbProviderFactory, string connectionString)
        {
            this.dbProviderFactory = dbProviderFactory;
            this.connectionString = connectionString;
        }

        private T CreateQuery<T>(Func<IDbCommand, T> func, int commandTimeout, CommandType commandType, string commandText, params object[] parameters)
        {

            using (IDbConnection connection = dbProviderFactory.CreateConnection())
            {
                connection.ConnectionString = connectionString;
                IDbCommand command = connection.CreateCommand();
                command.CommandText = commandText;

                foreach (var parameter in parameters)
                {
                    IDbDataParameter par = parameter as IDbDataParameter;
                    if (par == null)
                    {
                        par = command.CreateParameter();
                        par.Value = parameter;
                    }
                    command.Parameters.Add(par);
                }
                command.CommandType = commandType;
                command.CommandTimeout = commandTimeout;
                connection.Open();
                T retVal = func(command);
                connection.Close();
                return retVal;
            }
        }

        private IEnumerable<ResultSet> GetDataInternal(IDbCommand command)
        {
            using (IDataReader reader = command.ExecuteReader())
            {
                int index = 0;
                do
                {
                    ResultSet set = new ResultSet();
                    set.DataRecords = GetReaderEnumerator(reader);
                    set.Index = index++;
                    yield return set;
                } while (reader.NextResult());
            }
        }

        private IEnumerable<IDataRecord> GetReaderEnumerator(IDataReader reader)
        {
            while (reader.Read())
                yield return reader;
        }

        public int ExecuteNonQuery(string commandText, params object[] parameters)
        {
            return ExecuteNonQuery(CommandType.Text, commandText, parameters);
        }

        public int ExecuteNonQuery(CommandType commandType, string commandText, params object[] parameters)
        {
            return CreateQuery<int>((command) => command.ExecuteNonQuery(), 30, commandType, commandText, parameters);
        }

        public object ExecuteScalar(CommandType commandType, string commandText, params object[] parameters)
        {
            return CreateQuery<object>((command) => command.ExecuteScalar(), 30, commandType, commandText, parameters);
        }

        public T ExecuteScalar<T>(CommandType commandType, string commandText, params object[] parameters)
        {
            return SimpleConverter.Convert<T>(ExecuteScalar(commandType, commandText, parameters));
        }

        public IEnumerable<ResultSet> GetMultipleData(CommandType commandType, string commandText, params object[] parameters)
        {
            return CreateQuery<IEnumerable<ResultSet>>(GetDataInternal, 30, commandType, commandText, parameters);
        }

        public IEnumerable<IDataRecord> GetData(CommandType commandType, string commandText, params object[] parameters)
        {
            var resultSets = CreateQuery<IEnumerable<ResultSet>>(GetDataInternal, 30, commandType, commandText, parameters);
            using (var enumerator = resultSets.GetEnumerator())
            {
                if (enumerator.MoveNext())
                    return enumerator.Current.DataRecords;
                else
                    return new IDataRecord[0];
            }
        }

        public IEnumerable<T> GetData<T>(CommandType commandType, string commandText, params object[] parameters)
        {
            ObjectResolver.IObjectPopulator<T> populator = ObjectResolver.ObjectPopulator<T>.Get();
            return GetData(commandType, commandText, parameters).Select(x => populator.Populate(x));
        }

    }
}
